Refuse to spawn an extraction pool that would spawn its own (#1637) - #3620
ayushcodes10 wants to merge 12 commits into
Conversation
A guard less caller (no if __name__ == "__main__": block) makes every Windows spawned worker re execute the top level module on import. If that module calls extract() again at module scope, the worker opens its own pool, whose own guard less children do the same, faster than any per future BrokenProcessPool exception can surface and stop it. Two checks now run before the pool is ever opened: refuse unconditionally when already inside a multiprocessing child (a legitimate call only ever happens in the main process), and on Windows, skip the pool when the caller's own __main__ module has no guard, rather than only catching the failure after the fact. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers both new guards in _extract_parallel: refusing to open a pool from inside a spawned worker, and pre emptively declining the pool on Windows for a caller whose main module lacks the guard, while confirming a properly guarded caller still takes the pool path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Guards extract()'s parallel path against a fork bomb on guard-less Windows callers: _extract_parallel now refuses to open a ProcessPoolExecutor whenever multiprocessing.parent_process() shows it's already inside a spawned worker, and on Windows it also declines pre-emptively when the caller's __main__ source lacks an if __name__ == "__main__": guard (detected by _caller_main_lacks_guard, which treats an unreadable source as "can't tell"). Both cases warn and hand the work back for sequential extraction; a properly guarded Windows caller still takes the pool path.
Worth a look
- Windows fork-bomb guard is bypassed by any
__main__substring —graphify/extract.py:6393· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Guard detection treats any 'main' occurrence as a valid multiprocessing guard —
graphify/extract.py· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Windows guard check is bypassed by any 'main' string —
graphify/extract.py:6388· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Guard detection matches any 'main' substring, not the guard —
graphify/extract.py:6391· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Guard detection false-negative for any file containing the string 'main' —
graphify/extract.py:6392· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2474 functions depend on the 875 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 645 callers, 45 callees - new:
_rebuild_code()— 129 callers, 54 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
dispatch_command()— 2 callers, 125 callees - new:
_get_extractor()— 26 callers, 6 callees - new:
run_pipeline()— 8 callers, 13 callees - new:
collect_files()— 17 callers, 6 callees - …and 30 more — each is listed as a finding
Verification — 2474 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2299 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
286 of 286 test file(s) selected (100%) via static blast radius.
Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.
tests/test_affected_cli.py— full-run-safetytests/test_affected_member_seed.py— full-run-safetytests/test_agents_platform.py— full-run-safetytests/test_analyze.py— full-run-safetytests/test_anthropic_custom_endpoint.py— full-run-safetytests/test_antigravity_install.py— full-run-safetytests/test_apm_fallback_version.py— full-run-safetytests/test_architecture_doc.py— full-run-safetytests/test_astro_extraction.py— impact, full-run-safetytests/test_astro_import_ids.py— impact, full-run-safetytests/test_atomic_canvas_export.py— full-run-safetytests/test_atomic_version_stamp.py— full-run-safetytests/test_atomic_writes.py— full-run-safetytests/test_backend_env_isolation.py— full-run-safetytests/test_backend_extras.py— full-run-safetytests/test_benchmark.py— full-run-safetytests/test_benchmark_raw_graph.py— full-run-safetytests/test_build.py— impact, full-run-safetytests/test_build_merge_dedup_scope.py— full-run-safetytests/test_build_merge_hyperedges_and_prune.py— full-run-safetytests/test_build_merge_shrink_guard.py— full-run-safetytests/test_builtin_global_type_refs.py— impact, full-run-safetytests/test_cache.py— full-run-safetytests/test_callflow_html.py— full-run-safetytests/test_cargo_introspect.py— full-run-safetytests/test_carried_hyperedge_remap.py— full-run-safetytests/test_case_sensitive_resolution.py— impact, full-run-safetytests/test_charmap_encoding.py— full-run-safetytests/test_chunking.py— full-run-safetytests/test_cjs_module_extension.py— impact, full-run-safetytests/test_claude_cli_backend.py— full-run-safetytests/test_claude_md.py— full-run-safetytests/test_cli_broken_pipe.py— full-run-safetytests/test_cli_export.py— full-run-safetytests/test_cli_help.py— full-run-safetytests/test_cluster.py— full-run-safetytests/test_codebuddy.py— full-run-safetytests/test_community_hub_labels.py— full-run-safetytests/test_community_labels_skill.py— full-run-safetytests/test_confidence.py— full-run-safetytests/test_corrupt_graph_json.py— full-run-safetytests/test_cpp_nested_and_cli.py— impact, full-run-safetytests/test_cpp_objc_cross_file_calls.py— impact, full-run-safetytests/test_cpp_preprocess.py— full-run-safetytests/test_cross_extension_reexport_self_cycle.py— impact, full-run-safetytests/test_cross_language_call_resolution.py— impact, full-run-safetytests/test_cross_repo_external_call_guards.py— impact, full-run-safetytests/test_cross_repo_member_calls.py— impact, full-run-safetytests/test_cross_repo_shared_types.py— full-run-safetytests/test_csharp_call_site_generic_args.py— impact, full-run-safety- … and 236 more
non-code file(s) changed (
CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)
changed code file(s) with no mapped test (
CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify \_extract\_parallel.
The verifier did not have enough to check \_extract\_parallel, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
· 38 more finding(s) on lines outside this diff (see the check run).
A review on this PR flagged that the guard less caller check used plain substring containment: any mention of __main__ anywhere in the caller's source, including a comment, docstring, or unrelated string literal, made the check report a guard that was not actually there, defeating the fork bomb protection this function exists to provide. Now matches the actual if statement (either operand order), so only a real guard counts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers the false positive a plain reviewer found (an unrelated mention of __main__ in a comment or docstring must not be read as a guard) and the reversed operand order, which is valid Python and must still be recognized as a real guard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Good catch — fixed. The guard check used a bare substring test, so any mention of |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Prevents a fork-bomb when extract() runs in parallel from a guard-less Windows caller script: _extract_parallel now refuses to open a ProcessPoolExecutor whenever it detects it is already inside a multiprocessing child (via multiprocessing.parent_process(), on any platform), and on Windows pre-emptively declines with a stderr warning when the __main__ module's source lacks an if __name__ == "__main__": guard, falling back to sequential extraction. The guard detection in _caller_main_lacks_guard matches the actual guard statement with _MAIN_GUARD_RE rather than a substring, so a __main__ mention in a comment, docstring, or string literal isn't mistaken for a real guard, and a source-read failure is treated as "can't tell" rather than "missing". Guarded callers keep the pool path and their parallelism.
Worth a look
- Guard detector treats triple-quoted string contents as a real main guard —
graphify/extract.py:6370· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Guard detector treats docstring example as real main guard —
graphify/extract.py:6394· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Guard detector rejects valid parenthesized main guard —
graphify/extract.py:6372· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2479 functions depend on the 880 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 645 callers, 45 callees - new:
_rebuild_code()— 129 callers, 54 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
dispatch_command()— 2 callers, 125 callees - new:
_get_extractor()— 26 callers, 6 callees - new:
run_pipeline()— 8 callers, 13 callees - new:
collect_files()— 17 callers, 6 callees - …and 30 more — each is listed as a finding
Verification — 2479 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2304 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
286 of 286 test file(s) selected (100%) via static blast radius.
Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.
tests/test_affected_cli.py— full-run-safetytests/test_affected_member_seed.py— full-run-safetytests/test_agents_platform.py— full-run-safetytests/test_analyze.py— full-run-safetytests/test_anthropic_custom_endpoint.py— full-run-safetytests/test_antigravity_install.py— full-run-safetytests/test_apm_fallback_version.py— full-run-safetytests/test_architecture_doc.py— full-run-safetytests/test_astro_extraction.py— impact, full-run-safetytests/test_astro_import_ids.py— impact, full-run-safetytests/test_atomic_canvas_export.py— full-run-safetytests/test_atomic_version_stamp.py— full-run-safetytests/test_atomic_writes.py— full-run-safetytests/test_backend_env_isolation.py— full-run-safetytests/test_backend_extras.py— full-run-safetytests/test_benchmark.py— full-run-safetytests/test_benchmark_raw_graph.py— full-run-safetytests/test_build.py— impact, full-run-safetytests/test_build_merge_dedup_scope.py— full-run-safetytests/test_build_merge_hyperedges_and_prune.py— full-run-safetytests/test_build_merge_shrink_guard.py— full-run-safetytests/test_builtin_global_type_refs.py— impact, full-run-safetytests/test_cache.py— full-run-safetytests/test_callflow_html.py— full-run-safetytests/test_cargo_introspect.py— full-run-safetytests/test_carried_hyperedge_remap.py— full-run-safetytests/test_case_sensitive_resolution.py— impact, full-run-safetytests/test_charmap_encoding.py— full-run-safetytests/test_chunking.py— full-run-safetytests/test_cjs_module_extension.py— impact, full-run-safetytests/test_claude_cli_backend.py— full-run-safetytests/test_claude_md.py— full-run-safetytests/test_cli_broken_pipe.py— full-run-safetytests/test_cli_export.py— full-run-safetytests/test_cli_help.py— full-run-safetytests/test_cluster.py— full-run-safetytests/test_codebuddy.py— full-run-safetytests/test_community_hub_labels.py— full-run-safetytests/test_community_labels_skill.py— full-run-safetytests/test_confidence.py— full-run-safetytests/test_corrupt_graph_json.py— full-run-safetytests/test_cpp_nested_and_cli.py— impact, full-run-safetytests/test_cpp_objc_cross_file_calls.py— impact, full-run-safetytests/test_cpp_preprocess.py— full-run-safetytests/test_cross_extension_reexport_self_cycle.py— impact, full-run-safetytests/test_cross_language_call_resolution.py— impact, full-run-safetytests/test_cross_repo_external_call_guards.py— impact, full-run-safetytests/test_cross_repo_member_calls.py— impact, full-run-safetytests/test_cross_repo_shared_types.py— full-run-safetytests/test_csharp_call_site_generic_args.py— impact, full-run-safety- … and 236 more
non-code file(s) changed (
CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)
changed code file(s) with no mapped test (
CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify \_extract\_parallel.
The verifier did not have enough to check \_extract\_parallel, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
· 38 more finding(s) on lines outside this diff (see the check run).
A formal review round on this PR found three real gaps in the regex this replaces: a guard shaped line sitting inside a triple quoted string or a docstring example was still read as a real guard, the exact false positive class this whole check exists to close, just needing more specific bait text to trigger. And a valid but less common parenthesized comparison was wrongly rejected as no guard at all, a regression from the plain substring check this branch started from. Now parses the caller's source with ast and looks for a real if statement whose test compares __name__ to the string "__main__" in either order. The parser never sees string or comment contents as code at all, and parens are transparent to it, so both gaps close at once. A source that fails to parse is treated the same as an unreadable file, matching the existing best effort philosophy here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers the three review reported gaps directly: guard text inside a triple quoted string, guard text inside a docstring example, and a parenthesized comparison, plus an unparseable caller falling back to the existing best effort treatment instead of being escalated. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Good catches — fixed both. The regex approach had two real gaps: a guard-shaped line sitting inside a triple-quoted string or docstring example was still read as a real guard (the exact false positive class this check exists to close, just needing more specific bait text), and a valid parenthesized comparison was wrongly rejected as no guard at all. Switched from a regex to parsing the caller's source with |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 4 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Prevents an unbounded process spawn on Windows when a guard-less caller script re-invokes extract() at module scope: _extract_parallel now refuses to open a ProcessPoolExecutor unconditionally when already running inside a multiprocessing child, and pre-emptively declines on Windows (warning to stderr) when the caller's __main__ module lacks an if __name__ == "__main__": guard, falling back to sequential extraction in both cases. _caller_main_lacks_guard parses the caller's source and looks for a real if statement whose test compares __name__ to "__main__" via _is_main_guard_test, so a guard-shaped line inside a docstring or comment no longer counts and a parenthesized comparison still does. Unreadable or unparseable caller source is treated as "can't tell" and does not disable the pool.
Worth a look
- Main-guard detection treats unprotecting guards as safe —
graphify/extract.py:6426· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Windows main-guard check accepts guards that do not protect module-scope extract calls —
graphify/extract.py:6427· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Nested main guard bypasses spawn-safety check —
graphify/extract.py:6429· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Windows spawn guard check accepts unrelated nested guards —
graphify/extract.py:6431· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2493 functions depend on the 894 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 645 callers, 45 callees - new:
_rebuild_code()— 129 callers, 54 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
dispatch_command()— 2 callers, 125 callees - new:
_get_extractor()— 26 callers, 6 callees - new:
run_pipeline()— 8 callers, 13 callees - new:
collect_files()— 17 callers, 6 callees - …and 30 more — each is listed as a finding
Verification — 2493 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2318 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
286 of 286 test file(s) selected (100%) via static blast radius.
Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.
tests/test_affected_cli.py— full-run-safetytests/test_affected_member_seed.py— full-run-safetytests/test_agents_platform.py— full-run-safetytests/test_analyze.py— full-run-safetytests/test_anthropic_custom_endpoint.py— full-run-safetytests/test_antigravity_install.py— full-run-safetytests/test_apm_fallback_version.py— full-run-safetytests/test_architecture_doc.py— full-run-safetytests/test_astro_extraction.py— impact, full-run-safetytests/test_astro_import_ids.py— impact, full-run-safetytests/test_atomic_canvas_export.py— full-run-safetytests/test_atomic_version_stamp.py— full-run-safetytests/test_atomic_writes.py— full-run-safetytests/test_backend_env_isolation.py— full-run-safetytests/test_backend_extras.py— full-run-safetytests/test_benchmark.py— full-run-safetytests/test_benchmark_raw_graph.py— full-run-safetytests/test_build.py— impact, full-run-safetytests/test_build_merge_dedup_scope.py— full-run-safetytests/test_build_merge_hyperedges_and_prune.py— full-run-safetytests/test_build_merge_shrink_guard.py— full-run-safetytests/test_builtin_global_type_refs.py— impact, full-run-safetytests/test_cache.py— full-run-safetytests/test_callflow_html.py— full-run-safetytests/test_cargo_introspect.py— full-run-safetytests/test_carried_hyperedge_remap.py— full-run-safetytests/test_case_sensitive_resolution.py— impact, full-run-safetytests/test_charmap_encoding.py— full-run-safetytests/test_chunking.py— full-run-safetytests/test_cjs_module_extension.py— impact, full-run-safetytests/test_claude_cli_backend.py— full-run-safetytests/test_claude_md.py— full-run-safetytests/test_cli_broken_pipe.py— full-run-safetytests/test_cli_export.py— full-run-safetytests/test_cli_help.py— full-run-safetytests/test_cluster.py— full-run-safetytests/test_codebuddy.py— full-run-safetytests/test_community_hub_labels.py— full-run-safetytests/test_community_labels_skill.py— full-run-safetytests/test_confidence.py— full-run-safetytests/test_corrupt_graph_json.py— full-run-safetytests/test_cpp_nested_and_cli.py— impact, full-run-safetytests/test_cpp_objc_cross_file_calls.py— impact, full-run-safetytests/test_cpp_preprocess.py— full-run-safetytests/test_cross_extension_reexport_self_cycle.py— impact, full-run-safetytests/test_cross_language_call_resolution.py— impact, full-run-safetytests/test_cross_repo_external_call_guards.py— impact, full-run-safetytests/test_cross_repo_member_calls.py— impact, full-run-safetytests/test_cross_repo_shared_types.py— full-run-safetytests/test_csharp_call_site_generic_args.py— impact, full-run-safety- … and 236 more
non-code file(s) changed (
CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)
changed code file(s) with no mapped test (
CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify \_extract\_parallel.
The verifier did not have enough to check \_extract\_parallel, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
· 38 more finding(s) on lines outside this diff (see the check run).
A fresh review round found that ast.walk() finds a guard anywhere in the tree, including one nested inside an unrelated function, class, or dead branch. Such a guard never actually runs at import time and protects nothing, so a caller whose real module scope code is fully unguarded could still be treated as safe, letting the exact fork bomb scenario this check exists to prevent happen anyway. The guard idiom only has its intended effect as a bare top level statement, so only the module's direct top level statements are checked now, not every node anywhere in the source. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers a guard nested inside an unrelated function, alongside a genuinely unguarded module scope extract() call, the exact combination that let ast.walk() report a guard where there was none. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Good catch — fixed. The guard idiom only has its intended effect as a bare top-level statement, so now only the module's direct top-level statements ( |
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Refuses to open a ProcessPoolExecutor from _extract_parallel when already running inside a multiprocessing child, and on Windows also declines pre-emptively (falling back to sequential extraction with a stderr warning) when the caller's __main__ module lacks an if __name__ == "__main__": guard, preventing the runaway process spawning a guard-less spawn-start caller triggers. Detects the guard by AST-parsing the caller's source via _caller_main_lacks_guard and _is_main_guard_test, matching only a real top-level if comparing __name__ to "__main__" in either operand order — so guard-shaped text in docstrings or comments no longer counts, and parenthesized comparisons do; an unreadable or unparsable source is treated as "can't tell" rather than "missing".
Worth a look
- Unrelated top-level main guard lets unsafe module-scope extract run in parallel —
graphify/extract.py:6431· Escalate · high- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Parallel extract now silently returns sequentially inside any multiprocessing child —
graphify/extract.py:6462· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 2496 functions depend on the 897 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 645 callers, 45 callees - new:
_rebuild_code()— 129 callers, 54 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
dispatch_command()— 2 callers, 125 callees - new:
_get_extractor()— 26 callers, 6 callees - new:
run_pipeline()— 8 callers, 13 callees - new:
collect_files()— 17 callers, 6 callees - …and 30 more — each is listed as a finding
Verification — 2496 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2321 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
286 of 286 test file(s) selected (100%) via static blast radius.
Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.
tests/test_affected_cli.py— full-run-safetytests/test_affected_member_seed.py— full-run-safetytests/test_agents_platform.py— full-run-safetytests/test_analyze.py— full-run-safetytests/test_anthropic_custom_endpoint.py— full-run-safetytests/test_antigravity_install.py— full-run-safetytests/test_apm_fallback_version.py— full-run-safetytests/test_architecture_doc.py— full-run-safetytests/test_astro_extraction.py— impact, full-run-safetytests/test_astro_import_ids.py— impact, full-run-safetytests/test_atomic_canvas_export.py— full-run-safetytests/test_atomic_version_stamp.py— full-run-safetytests/test_atomic_writes.py— full-run-safetytests/test_backend_env_isolation.py— full-run-safetytests/test_backend_extras.py— full-run-safetytests/test_benchmark.py— full-run-safetytests/test_benchmark_raw_graph.py— full-run-safetytests/test_build.py— impact, full-run-safetytests/test_build_merge_dedup_scope.py— full-run-safetytests/test_build_merge_hyperedges_and_prune.py— full-run-safetytests/test_build_merge_shrink_guard.py— full-run-safetytests/test_builtin_global_type_refs.py— impact, full-run-safetytests/test_cache.py— full-run-safetytests/test_callflow_html.py— full-run-safetytests/test_cargo_introspect.py— full-run-safetytests/test_carried_hyperedge_remap.py— full-run-safetytests/test_case_sensitive_resolution.py— impact, full-run-safetytests/test_charmap_encoding.py— full-run-safetytests/test_chunking.py— full-run-safetytests/test_cjs_module_extension.py— impact, full-run-safetytests/test_claude_cli_backend.py— full-run-safetytests/test_claude_md.py— full-run-safetytests/test_cli_broken_pipe.py— full-run-safetytests/test_cli_export.py— full-run-safetytests/test_cli_help.py— full-run-safetytests/test_cluster.py— full-run-safetytests/test_codebuddy.py— full-run-safetytests/test_community_hub_labels.py— full-run-safetytests/test_community_labels_skill.py— full-run-safetytests/test_confidence.py— full-run-safetytests/test_corrupt_graph_json.py— full-run-safetytests/test_cpp_nested_and_cli.py— impact, full-run-safetytests/test_cpp_objc_cross_file_calls.py— impact, full-run-safetytests/test_cpp_preprocess.py— full-run-safetytests/test_cross_extension_reexport_self_cycle.py— impact, full-run-safetytests/test_cross_language_call_resolution.py— impact, full-run-safetytests/test_cross_repo_external_call_guards.py— impact, full-run-safetytests/test_cross_repo_member_calls.py— impact, full-run-safetytests/test_cross_repo_shared_types.py— full-run-safetytests/test_csharp_call_site_generic_args.py— impact, full-run-safety- … and 236 more
non-code file(s) changed (
CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)
changed code file(s) with no mapped test (
CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify \_extract\_parallel.
The verifier did not have enough to check \_extract\_parallel, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set
· 38 more finding(s) on lines outside this diff (see the check run).
Summary
Fixes #1637. On Windows, a caller script with no
if __name__ == "__main__":guard turnsextract()'s parallel path into a fork bomb rather than a slow failure: every spawned worker re-executes the top-level module on import, and if that module callsextract()again at module scope (the common shape of a quick inline runner script), the worker opens its ownProcessPoolExecutor, whose own guard-less children do the same. A second reporter measured this directly — ~1,278python.exeprocesses and ~28 GB consumed before the machine had to be power-cycled.Root cause
_extract_parallelalready caughtBrokenProcessPooland fell back to sequential extraction, but only after the pool tried to run.ProcessPoolExecutor._adjust_process_count()respawns dying workers as they die, and on a guard-less caller every respawned worker dies the same way (re-importing__main__triggers_check_not_importing_main) and — critically — re-executes the module-levelextract()call, opening yet another pool. The pool keeps minting doomed processes faster than any per-future exception can propagate and stop it, so the reactiveexcept BrokenProcessPoolhandler never gets a chance to run.Fix
Two checks now run in
_extract_parallelbefore aProcessPoolExecutoris ever constructed:multiprocessing.parent_process() is not None). A legitimate call to_extract_parallelonly ever happens in the main process — a worker should only ever run_extract_single_filevia the pool machinery — so this is always safe and breaks the specific recursive-spawn mechanism regardless of platform.__main__module's source lacks the__main__guard text, instead of discovering the failure only after the pool has already started respawning. Scoped tosys.platform == "win32"(matching the existing Windows-specific worker cap already in this function) since macOS/Linux typically default to fork, where this class of guard-less-caller re-execution doesn't occur the same way, and scoping it avoids changing extraction behavior on non-Windows CI.Both checks fall back to sequential extraction exactly like the existing
BrokenProcessPoolhandler — correct output, just without the pool.Test plan
test_extract_parallel_declines_pool_inside_a_spawned_worker,test_extract_parallel_declines_pool_on_windows_when_caller_lacks_guard, andtest_extract_parallel_still_spawns_pool_on_windows_when_caller_has_guardtotests/test_extract.py.python3 -m pytest -q— 5624 passed, 68 skipped, no regressions.python3 -m tools.skillgen --check— OK.🤖 Generated with Claude Code